package com.ztgf.search.server.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Maps;
import com.ztgf.search.models.bean.Constant;
import com.ztgf.search.models.bean.SearchRequestBean;
import com.ztgf.search.models.bean.SearchResponseBean;
import com.ztgf.search.models.dto.EsShopDTO;
import com.ztgf.search.models.service.IShopSearchService;
import com.ztgf.search.server.utils.CollectionHelp;
import com.ztgf.search.server.utils.StringHelper;
import com.ztgf.shopping.models.service.shop.IShopInfoService;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.annotation.DubboService;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.core.CountRequest;
import org.elasticsearch.client.core.CountResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchPhraseQueryBuilder;
import org.elasticsearch.index.query.MultiMatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.ScoreSortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.annotation.Async;

import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static com.ztgf.common.utils.UtilConstants.isNull;

/*
 * 店铺搜索实现
 * */
@Slf4j
@DubboService
public class ShopSearchServiceImpl implements IShopSearchService {

    @Qualifier("restHighLevelClient")
    @Autowired
    public RestHighLevelClient client;
    @Autowired
    private ElasticsearchServiceImpl elasticsearchService;
    @DubboReference
    private IShopInfoService shopInfoService;
    // 一个批次的最大数据量
    private static final int batchImportSize = 40000;
    // 别名
    private static final String aliasename = "index_shop";
    // 类型名称
    private static final String typeName = "shop";

    @Override
    public SearchResponseBean searchShop(String aliasName, String type, String searchFields, SearchRequestBean searchRequestBean) {
        Map<String, Float> fields = getFieldsMap(searchFields);
        SearchResponseBean searchResponseBean = new SearchResponseBean();
        SearchRequest searchRequest = new SearchRequest(aliasName);
        searchRequest.types(type);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        // 排序规则
        ScoreSortBuilder sortBuilder = SortBuilders.scoreSort().order(SortOrder.DESC);
        searchSourceBuilder.sort(sortBuilder);
        // 分页
        if (searchRequestBean.getPageSize() != null && searchRequestBean.getPageNo() != null) {
            searchSourceBuilder.from((searchRequestBean.getPageNo() - 1) * searchRequestBean.getPageSize());
            searchSourceBuilder.size(searchRequestBean.getPageSize());
        } else {
            searchSourceBuilder.from(0);
            searchSourceBuilder.size(50);
        }
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        // 关键词
        if (StringHelper.isNotEmpty(searchRequestBean.getGoodsKeyWord())) {
            // 处理高亮字段
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("shopNameIkSmart");
            highlightBuilder.field("shopNameIkMaxWord");
            highlightBuilder.requireFieldMatch(true);
            // 前缀后缀
            highlightBuilder.preTags("<span style='color:red'>");
            highlightBuilder.postTags("</span>");
            searchSourceBuilder.highlighter(highlightBuilder);
            // 查询条件
            /*String shopName = searchRequestBean.getGoodsKeyWord().replaceAll(Constant.BLANKSTR, "");
            MultiMatchQueryBuilder multiQuery = QueryBuilders.multiMatchQuery(StringHelper.removeBlank(shopName)).fields(fields);
            MatchPhraseQueryBuilder matchPhraseQuery = QueryBuilders.matchPhraseQuery("shopName", searchRequestBean.getGoodsKeyWord());
            boolQuery.must(multiQuery).should(matchPhraseQuery);*/
            String[] split = searchRequestBean.getGoodsKeyWord().split(Constant.BLANKSTR);
            for (String key : split) {
                if (StringHelper.isNotEmpty(key)) {
                    MultiMatchQueryBuilder multiMatchQueryBuilder = QueryBuilders.multiMatchQuery(StringHelper.removeBlank(key)).fields(fields);
                    // 构建查询
                    boolQuery.must(multiMatchQueryBuilder);
                }
            }
        }
        searchSourceBuilder.query(boolQuery);
        searchRequest.source(searchSourceBuilder);
        try {
            SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
            SearchHits hits = searchResponse.getHits();

            Iterator<SearchHit> iterator = hits.iterator();

            while (iterator.hasNext()) {
                SearchHit searchHit = iterator.next(); // 每个查询对象

//                System.out.println("sss="+searchHit.getSourceAsString()); // 获取字符串格式打印
            }



            SearchHit[] searchHits = hits.getHits();
            if (CollectionHelp.isArrayNotEmpty(searchHits)) {
                for (SearchHit hit : searchHits){
                    // 原来的数据
                    Map<String, Object> sourceAsMap = hit.getSourceAsMap();
                    if(null != sourceAsMap && sourceAsMap.size() > 0 && null != sourceAsMap.get("shopName")) {
                        log.info("----------shopId=" + sourceAsMap.get("id").toString() + "--------aliasName=" + aliasName + "-------type=" + type + "-------shopName=" + sourceAsMap.get("shopName").toString());
                    }
                    // 将店铺加入ES
                    List<EsShopDTO> esShop = shopInfoService.queryEsShop(sourceAsMap.get("id").toString());
                    if(null == esShop || esShop.size() == 0){
                        this.deleteData(aliasName,type,sourceAsMap.get("id").toString());
                    }
                    log.info("----------shopId2="+esShop.get(0).getId()+"--------merchantType="+esShop.get(0).getMerchantType()+"-------shopName="+esShop.get(0).getShopName());
                    if(esShop.get(0).getMerchantType() != sourceAsMap.get("merchantType")){
                        this.updateShop(sourceAsMap.get("id").toString(), aliasName, type, 0, esShop.size(), esShop);
                    }
                }
            }
            //重新查询赋值
            searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
            hits = searchResponse.getHits();
            searchHits = hits.getHits();
            if (CollectionHelp.isArrayNotEmpty(searchHits)) {
                for (SearchHit hit : searchHits) {
                    // 解析高亮的字段
                    Map<String, HighlightField> highlightFields = hit.getHighlightFields();
                    HighlightField shopNameIkSmart = highlightFields.get("shopNameIkSmart");
                    HighlightField shopNameIkMaxWord = highlightFields.get("shopNameIkMaxWord");
                    String shopName = this.updateHighlightField(shopNameIkSmart, shopNameIkMaxWord);
                    // 原来的数据
                    Map<String, Object> sourceAsMap = hit.getSourceAsMap();
                    if (StringHelper.isNotEmpty(shopName)) {
                        sourceAsMap.put("shopName", shopName);
                    }
                    EsShopDTO searchShopBean = JSONObject.parseObject(JSONObject.toJSONString(sourceAsMap), EsShopDTO.class);
                    log.info("---shopid="+searchShopBean.getId()+"-------------------"+searchShopBean.getMerchantType()+"-----------------------"+searchShopBean.getShopName());
                    searchShopBean.setScore(hit.getScore());
                    searchResponseBean.getListShopBean().add(searchShopBean);
                }
            }
            CountRequest countRequest = new CountRequest(aliasName);
            countRequest.types(type);
            SearchSourceBuilder countSourceBuilder = new SearchSourceBuilder();
            countSourceBuilder.query(boolQuery);
            countRequest.source(countSourceBuilder);
            CountResponse countResponse = client.count(countRequest, RequestOptions.DEFAULT);
            searchResponseBean.setTotalNum(countResponse.getCount());
            searchResponseBean.setPageNum(searchRequestBean.getPageNo().longValue());
            searchResponseBean.setSize(searchRequestBean.getPageSize().longValue());
        } catch (IOException e) {
            log.info("搜索店铺错误信息:{}", e.getMessage());
            e.printStackTrace();
        }
        return searchResponseBean;
    }

    @Override
    public boolean add() {
        List<EsShopDTO> list = shopInfoService.queryEsShop("0");
        if (isNull(list)) {
            log.info("未查询到店铺信息");
            return false;
        }
        try {
            this.batchPost(list.size(), 0, list);
            log.info("添加店铺到es成功");
            return true;
        } catch (IOException e) {
            log.info("添加店铺到es失败");
            e.printStackTrace();
        }
        return false;
    }

    @Async
    @Override
    public void addShop(String aliasName, String typeName, int begin, int end, List<EsShopDTO> totalList) {
        if (isNull(totalList)) {
            return;
        }
        List<EsShopDTO> postList = totalList.subList(begin, end);
        BulkRequest bulkRequest = new BulkRequest();
        for (EsShopDTO d : postList) {
            String jsonString = JSON.toJSONString(d);
            IndexRequest indexRequest = new IndexRequest(aliasName, typeName, String.valueOf(d.getId()));
            indexRequest.source(jsonString, XContentType.JSON);
            bulkRequest.add(indexRequest);
        }
        bulkRequest.timeout(TimeValue.timeValueMinutes(40));
        try {
            BulkResponse bulkResponse = client.bulk(bulkRequest, RequestOptions.DEFAULT);
            if (bulkResponse.hasFailures()) {
                log.error("数据批量添加失败！，失败信息：" + bulkResponse.buildFailureMessage() + ", status: "
                        + bulkResponse.status().getStatus());
            } else {
                log.info("数据批量添加成功！");
            }
        } catch (Exception e) {
            log.info("批量添加失败");
            e.printStackTrace();
        }
    }

    @Async
    @Override
    public void deleteData(String aliasName, String typeName, String id) {
        try {
            elasticsearchService.deleteData(aliasName, typeName, id);
        } catch (Exception e) {
            log.error("es删除店铺信息失败");
            e.printStackTrace();
        }
    }

    @Override
    public boolean checkIndex(String indexName) {
        GetIndexRequest request = new GetIndexRequest(indexName);
        try {
            boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
            if (exists) {
                return true;
            }
            // 如果不存在创建索引
            boolean index = elasticsearchService.createIndex(indexName, aliasename, 1, 1);
            if (!index) {
                return false;
            }
            // 索引创建成功后创建映射
            Class<?> clazz = EsShopDTO.class;
            boolean addMapping = elasticsearchService.addMapping(indexName, typeName, clazz);
            if (!addMapping) {
                return false;
            }
            // 映射创建成功后添加数据
            //return this.add();
            return true;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public void updateShop(String id, String aliasName, String type, int begin, int end, List<EsShopDTO> totalList) {
        try {
            // 判断记录是否存在
            boolean exist = elasticsearchService.isExist(aliasename, typeName, id);
            if (exist) {
                // 存在更新记录
                this.deleteData(aliasename, typeName, id);
            }
            this.addShop(aliasename, typeName, begin, end, totalList);
        } catch (Exception e) {
            log.error("更新es店铺信息错误:{}", e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 解析搜索字段
     *
     * @param searchFields
     * @return
     */
    private Map<String, Float> getFieldsMap(String searchFields) {
        String[] fieldsArray = searchFields.split(",");
        Map<String, Float> fields = Maps.newHashMap();
        for (String k : fieldsArray) {
            fields.put(k.split("\\^")[0], Float.parseFloat(k.split("\\^")[1]));
        }
        return fields;
    }

    /**
     * @Description: 批量发送数据
     * @Author: chenyu.wang
     * @Params: [size, begin, list]
     * @Date: 2020.11.18 09:40
     */
    private void batchPost(int size, int begin, List<EsShopDTO> list) throws IOException {
        if (size > batchImportSize) {
            this.addBatch(aliasename, typeName, begin, begin + batchImportSize, list);
            list = list.subList(begin + batchImportSize, list.size());
            this.batchPost(list.size(), 0, list);
        } else {
            this.addBatch(aliasename, typeName, begin, list.size(), list);
        }
    }

    /**
     * @Description: 批量添加信息
     * @Author: chenyu.wang
     * @Params: [aliasName, typeName, begin, end, totalList]
     * @Date: 2020.11.18 09:40
     */
    private boolean addBatch(String aliasName, String typeName, int begin, int end, List<EsShopDTO> totalList)
            throws IOException {
        List<EsShopDTO> postList = totalList.subList(begin, end);
        BulkRequest bulkRequest = new BulkRequest();
        for (EsShopDTO d : postList) {
            String jsonString = JSON.toJSONString(d);
            IndexRequest indexRequest = new IndexRequest(aliasName, typeName, String.valueOf(d.getId()));
            indexRequest.source(jsonString, XContentType.JSON);
            bulkRequest.add(indexRequest);
        }
        bulkRequest.timeout(TimeValue.timeValueMinutes(40));
        BulkResponse bulkResponse = client.bulk(bulkRequest, RequestOptions.DEFAULT);
        if (bulkResponse.hasFailures()) {
            log.error("数据批量添加失败！，失败信息：" + bulkResponse.buildFailureMessage() + ", status: "
                    + bulkResponse.status().getStatus());
        } else {
            log.info("数据批量添加成功！");
            return true;
        }
        return false;
    }

    /**
     * @Description: 处理高亮字段
     * @Author: chenyu.wang
     * @Params: [shopNameIkSmart, shopNameIkMaxWord]
     * @Date: 2020.12.28 16:30
     */
    private String updateHighlightField(HighlightField shopNameIkSmart, HighlightField shopNameIkMaxWord) {
        StringBuilder shopName = new StringBuilder();
        if (shopNameIkSmart != null && shopNameIkMaxWord == null) {
            Text[] texts = shopNameIkSmart.getFragments();
            for (Text text : texts) {
                shopName.append(text);
            }
            return shopName.toString();
        }
        if (shopNameIkSmart == null && shopNameIkMaxWord != null) {
            Text[] texts = shopNameIkMaxWord.getFragments();
            for (Text text : texts) {
                shopName.append(text);
            }
            return shopName.toString();
        }
        if (shopNameIkSmart != null && shopNameIkMaxWord != null) {
            Text[] ikMaxWordFragments = shopNameIkMaxWord.getFragments();
            for (Text ikMaxWordFragment : ikMaxWordFragments) {
                shopName.append(ikMaxWordFragment);
            }
            return shopName.toString();
        }
        return shopName.toString();
    }
}
